#pragma once

#include "BasicTypes.h"
#include "Definitions.h"

#include <functional>
#include <string>
#include <unordered_map>

namespace MOE
{
    namespace Core
    {
        class Field;
        class Method;

        // An object storing class meta.
        // This object contains all meta-fields and meta-methods of a meta-enabled object.
        // Note that this object may only be constructed / added on. It's not designed to be removed during execution.
        // Thus it does not handle cases such as a class in the middle of inheritance chain getting deleted (leaving any class inheriting it dangling).
		//
		// Basic types are also registered with their own ClassMeta objects.
		// This way the Type class can be very generic by simply referencing to a ClassMeta object containing the actual type.
        class ClassMeta
        {
		public:
			// Defines a read/write function for one object of this type.
			typedef void (*CopyDataFunction)(tUntypedConstRef, tUntypedRef);

			// Defines a read/write function for an array of objects of this type.
			typedef void (*CopyArrayFunction)(tUntypedConstRef, tUntypedRef, tSize);

		public:
			// Defines a default instance - a class meta for Nil type.
			static const ClassMeta kNil;

        public:
			// Default constructor.
			// This constructs a ClassMeta for Nil type.
			ClassMeta();

			// Constructor for basic types
			ClassMeta(const char8* className, BasicType basicTypeEnum, tSize elementSize, CopyDataFunction elementHandler, CopyArrayFunction arrayHandler);

			// Constructor for composite types
			ClassMeta(const char8* className, tSize elementSize);

			// Constructor for composite types that inherits from a parent class
			ClassMeta(const ClassMeta& parentClass, const char8* className, tSize elementSize);

			// Destructor cleans up all Fields and Methods.
			~ClassMeta();

			// Get the class name
			const char8* GetClassName() const { return mClassName.c_str(); }

			// Checks if this ClassMeta is Nil type. This is the default "invalid" type.
			bool IsNil() const { return mIsBasicType && mBasicType == BasicType::kNil; }

			// Checks if this ClassMeta is a BasicType.
			bool IsBasicType() const { return mIsBasicType; }

			// Get the basic type enum of this class meta.
			BasicType GetBasicType() const { return mBasicType; }

			// Gets the size of an instance of this class.
			tSize GetSize() const { return mClassSize; }

            // Gets the class's parent class.
			const ClassMeta& GetParentClass() const { return mParentClass; }

			// Helper function to check if this class is a subclass of another class.
			bool IsSubclassOf(const ClassMeta& otherMeta) const;

            // Adds a field to the class meta.
            void AddField(Field* field);

            // Retrieves a field with specified name.
            const Field& GetField(const char8* fieldName) const;

            // Adds a method to the class meta.
            void AddMethod(Method* method);

            // Retrieves a method with specified name.
            const Method& GetMethod(const char8* methodName) const;

			// Gets the reference of Nth element from a value array.
			tUntypedConstRef GetElement(tUntypedConstRef arrayRef, tSize n) const;
			tUntypedRef GetElement(tUntypedRef arrayRef, tSize n) const;

			// Copies the value of source and save it to destination.
			// Both source and destination must be able to hold the object defined by this ClassMeta.
			void CopyValue(tUntypedConstRef source, tUntypedRef destination) const;

			// Copies an array of N elements from source to destination.
			void CopyArray(tUntypedConstRef source, tUntypedRef destination, tSize numElements) const;

            // Iterate over all fields.
			void ForEachField(std::function<void(const Field&)>& func) const;

            // Iterate over all methods.
            void ForEachMethod(std::function<void(const Method&)>& func) const;

		private:
			std::string mClassName;

			// Is this class a basic type?
			bool mIsBasicType;

			// Basic type enum.
			BasicType mBasicType;

			// Only used if I'm a basic type.
			CopyDataFunction mCopyElementHandler;
			CopyArrayFunction mCopyArrayHandler;

			// Gets the class's size.
			tSize mClassSize;

			// Sets a class as this class's parent class.
			// We only support single inheritance, and no interface yet.
			const ClassMeta& mParentClass;

			typedef std::unordered_map<tHashValue, Field*> tFieldMap;

			// List of all fields.
			tFieldMap mFields;

			typedef std::unordered_map<tHashValue, Method*> tMethodMap;

			// List of all methods.
			tMethodMap mMethods;
        };
    }
}
